/* Copyright (c) 2007-2008, Jeffrey R Griffin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Jeffrey R Griffin nor the
*       names of his contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Jeffrey R Griffin ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package net.visibleblue.android.serialintents;

import java.io.IOException;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;

import net.visibleblue.android.os.PersistentParcel;
import net.visibleblue.android.paranoid.Actions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.net.ContentURI;
import android.os.BinderNative;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;

public class SerialIntents extends Service implements Runnable {

	public static final String DESCRIPTION_KEY = "SIDesKey";
	public static final String ORDER_INDEX_KEY = "SIOIndexKey";
	public static final String INTENTS_SCHEME = "intents";
	public static final String INPUT_INTENTS = "InputIntents";
	static final String RUN_INTENT = "RunIntent";
	public static final String NOTIFICATION_ID = "NotificationID";
	private NotificationManager _nm;
	private int _id;
	private static final HashMap<Integer, SerialIntents> _objectMap;
	private Object _synchObj = new Object();
	private volatile Looper _serviceLooper;
	private volatile Handler _serviceHandler;
	private int _count = 0;
	private final HashMap<Integer, Intent> _orderMap = new HashMap<Integer, Intent>();

	static
	{
		_objectMap = new HashMap<Integer, SerialIntents>();
	}

//	private static ResolveInfo resolveIntent(PackageManager packMan, Intent intent)
//	{
//		if(intent==null)
//			return null;
//		List<ResolveInfo> resolved = packMan.queryIntentReceivers(intent, 0);
//		if(resolved==null || resolved.size()==0)
//		{
//			resolved = packMan.queryIntentActivities(intent, 0);
//			if(resolved==null || resolved.size()==0)
//			{
//				resolved = packMan.queryIntentServices(intent, 0);
//				if(resolved==null || resolved.size()==0)
//					return null;
//			}
//		}
//		return resolved.get(0);
//	}

	private void launchIntent(Intent info)
	{
		ContentURI uri = info.getData();
		if(uri!=null && uri.getScheme().toLowerCase().equals(INTENTS_SCHEME))
		{
			URL dlurl;
			try
			{
				dlurl = new URL(uri.toString().substring(8));
			}
			catch(MalformedURLException muEx)
			{
				throw new RuntimeException(muEx);
			}
			Intent[] intents = downloadIntents(dlurl);
			for(int i=0; i<intents.length; i++)
			{
				//hardcoded security check
				if(intents[i].getAction().equals(Actions.INSERT))
					throw new RuntimeException("You cannot insert into the Paranoid(Android) public key store with a web deployed intent.");
				launchIntent(intents[i]);
			}
			return;
		}
		
		Integer index = (Integer)info.getExtra(ORDER_INDEX_KEY);
		if(index!=null && index.intValue()!=_count)
		{
			_orderMap.put(index, info);
			return;
		}

		Intent clickIntent = new Intent();
		clickIntent.setClass(this, NotificationActivity.class);
		Bundle extras = new Bundle();
		//storeIntentInfo(info, extras);
		extras.putIntent(RUN_INTENT, info);
		extras.putInteger(NOTIFICATION_ID, new Integer(_id));
		clickIntent.putExtras(extras);

		_count++;
		_nm.notify(_id, new Notification(R.drawable.icon, 
				(CharSequence)info.getExtra(DESCRIPTION_KEY), clickIntent,
				null, null));
		synchronized (_synchObj) 
		{
			try
			{
				_synchObj.wait();
			}
			catch(InterruptedException iEx){}
		}
		_nm.cancel(_id);
		
		while(_orderMap.containsKey(_count))
			launchIntent(_orderMap.get(_count));
	}

	private static Intent[] downloadIntents(URL url)
	{
		try
		{
			URLConnection conn = url.openConnection();
			conn.connect();
			PersistentParcel ppl = new PersistentParcel(SerialIntents.class.getClassLoader(),
					conn.getInputStream());
			Object obj = ppl.readObject();
			if(obj instanceof Intent)
				return new Intent[]{(Intent)obj};
			else
				return (Intent[])obj;
		}
		catch(IOException ioEx)
		{
			throw new RuntimeException(ioEx);
		}
	}

	static void finishNotification(Integer id)
	{
		SerialIntents si = _objectMap.get(id);
		synchronized (si._synchObj) {
			si._synchObj.notify();
		}
	}

	@Override
	protected void onStart(int arg1, Bundle arg2)
	{
		while (_serviceHandler == null) {
			synchronized (this) {
				try {
					wait(100);
				} catch (InterruptedException e) {
				}
			}
		}

		Message msg = new Message();
		msg.obj = arg2;
		_serviceHandler.sendMessage(msg);
	}

	@Override
	protected void onCreate() {
		_nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		Thread thr = new Thread(null, this, "SerialIntents");
		thr.start();
	}

	@Override
	protected void onDestroy() {
		while (_serviceLooper == null) {
			synchronized (this) {
				try {
					wait(100);
				} catch (InterruptedException e) {
				}
			}
		}
		_serviceLooper.quit();
		_nm.cancel(_id);
	}

	public void run() {
		Looper.prepare();

		_serviceLooper = Looper.myLooper();
		_serviceHandler = new Handler()
		{
			@Override
			public void handleMessage(Message msg)
			{
				Bundle arguments = (Bundle)msg.obj;
				//IntentInfo[] input = getIntentInfoArray(arguments);
				//Intent[] input = (Intent[])ParcelableArray.getArray(arguments.getParcelable(INPUT_INTENTS));
				Intent[] input = getIntentArray(arguments);
				_id = arguments.getInteger(NOTIFICATION_ID).intValue();
				_objectMap.put(new Integer(_id), SerialIntents.this);

				try {
					for(int i=0; i<input.length; i++)
					{
						launchIntent(input[i]);
					}
				} catch (Exception ex) {
					int i=0;
				}
				// Done with our work...  stop the service!
				SerialIntents.this.stopSelf();
			}
		};

		Looper.loop();
	}

	@Override
	public IBinder getBinder() {
		return _binder;
	}

	// This is the object that receives interactions from clients.  See
	// RemoteService for a more complete example.
	private final IBinder _binder = new BinderNative() {
		@Override
		protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
			return super.onTransact(code, data, reply, flags);
		}
	};

	//This is needed temporarily, because the use of custom parcelables appears to be
	//broken
	public static void storeIntentArray(Intent[] info, Bundle bundle)
	{
		for(int i=0; i<info.length; i++)
		{
			bundle.putIntent(INPUT_INTENTS+i, info[i]);
		}
	}

//	public static void storeIntentInfoArray(IntentInfo[] info, Bundle bundle)
//	{
//	for(int i=0; i<info.length; i++)
//	{
//	bundle.putString(INPUT_INTENTS+"_Description_"+i, info[i].Description);
//	if(info[i].Intent!=null)
//	bundle.putParcelable(INPUT_INTENTS+"_Intent_"+i, info[i].Intent);
//	}
//	}

//	public static void storeIntentInfoArray(IntentInfo[] info, Bundle bundle)
//	{
//	for(int i=0; i<info.length; i++)
//	{
//	bundle.putString(INPUT_INTENTS+"_Description_"+i, info[i].Description);
//	if(info[i].Intent!=null)
//	bundle.putParcelable(INPUT_INTENTS+"_Intent_"+i, info[i].Intent);
//	}
//	}

//	private static void storeIntentInfo(IntentInfo info, Bundle bundle)
//	{
//	bundle.putString(INPUT_INTENTS+"_Description", info.Description);
//	if(info.Intent!=null)
//	bundle.putParcelable(INPUT_INTENTS+"_Intent", info.Intent);
//	}

	private static Intent[] getIntentArray(Bundle bundle)
	{
		ArrayList<Intent> info = new ArrayList<Intent>();
		for(int i=0; bundle.containsKey(INPUT_INTENTS+i); i++)
		{
			info.add(bundle.getIntent(INPUT_INTENTS+i));
		}
		return (Intent[])info.toArray(new Intent[info.size()]);
	}

//	static IntentInfo getIntentInfo(Bundle bundle)
//	{
//	return new IntentInfo(
//	bundle.getString(INPUT_INTENTS+"_Description"),
//	bundle.getIntent(INPUT_INTENTS+"_Intent"));
//	}
}
